#!/usr/bin/env python3

# Copyright (c) 2020-2021 Fpemud <fpemud@sina.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import os
import pathlib
from bidict import bidict
from ..._util import Util, CopiedAndSymlinkedDir
from ..._errors import ConfigError
from ..._game import GameBase, GameSpec, HintForRunner, MatchContext
from ..._game_straits import GameStraitForDos, GameStraitNeedCacheDir, GameStraitNeedJoysticks


class F22Lightning2(GameBase, GameStraitForDos):

    def __init__(self, data_dir):
        GameBase.__init__(self, data_dir)

        self._addStrait(GameStraitNeedCacheDir())
        self._addStrait(GameStraitNeedJoysticks())

        self._spec = GameSpec()
        if True:
            self._spec.default_language = "en_US"
            self._spec.languages = [self._spec.default_language]
            self._spec.resolutions = ["640x480", "400x300", "300x200"]

        self._binFn = "F22.EXE"
        self._cfgFn = "F22.CFG"

    @property
    def spec(self):
        return self._spec

    @property
    def run_items(self):
        return ["main"]

    def get_title(self, lang):
        return "F22 Lightning 2"

    def _prepare(self, context):
        self._context = context

        hint = HintForRunner()
        hint.selected_joystick_indices = []

        try:
            self._targetGameDirObj = CopiedAndSymlinkedDir(self._context.cache_dirpath, [self._dataDir])
            for fn in os.listdir(self._dataDir):
                if fn != self._cfgFn:
                    self._targetGameDirObj.symlink(fn, recursive=True)

            cfgFnObj = _CfgFile(os.path.join(self._dataDir, self._cfgFn))
            if len(self._context.joysticks) > 0:
                # FIXME: in future we should be able to select one joystick from multiple joysticks
                assert len(self._context.joysticks) == 1

                c = self._context.joysticks[0]
                if c.get_vendor() == "microsoft" and c.get_model() == "xone-controller":
                    cfgFnObj.joystickType = "ch_flight_stick_pro"
                    cfgFnObj.throttle = "no"
                    cfgFnObj.footPedals = "yes"
                    hint.selected_joystick_indices.append(0)
                else:
                    # we don't support this joysticks
                    pass
            cfgFnObj.save(os.path.join(self._targetGameDirObj.path, self._cfgFn))
        except BaseException:
            self._cleanup(on_error=True)
            raise

        return hint

    @MatchContext.edit_or_run
    def get_language(self):
        return self._spec.default_language

    @MatchContext.edit_or_run
    def set_language(self, language):
        assert language == self._spec.default_language

    @MatchContext.run
    def apply_resolution(self, resolution):
        assert resolution == self._spec.resolutions[0]

    def _commit(self):
        pass

    def _getRunInfo(self, item):
        assert not self._context.edit_or_run
        assert item == Util.listGetOnlyItem(self.run_items)

        return self.RunInfo(self._targetGameDirObj.path, "C:\\" + self._binFn)

    def _cleanup(self, on_error=False):
        if hasattr(self, "_cfgFile"):
            del self._cfgFile
        if hasattr(self, "_targetGameDirObj"):
            del self._targetGameDirObj
        if hasattr(self, "_context"):
            del self._context


class _CfgFile:

    _joystickTypeDict = bidict({
        b'\x00': "none",
        b'\x01': "2_btn",
        b'\x02': "4_btn",
        b'\x03': "ch_flight_stick_pro",
        b'\x04': "thrust_master_mkii",
        b'\x05': "wingman_extreme",
        b'\x06': "side_winder",
    })

    _throttleTypeDict = bidict({
        b'\x00': "no",
        b'\x01': "yes",
        b'\x02': "reversed",
    })

    _footPedalTypeDict = bidict({
        b'\x00': "no",
        b'\x01': "yes",
    })

    _redOutBlackOutTypeDict = bidict({
        b'\x00': "no",
        b'\x01': "yes",
    })

    def __init__(self, path):
        self._path = path

        self.sfxVolume = None       # range: [0x00, 0x1F],               default: 0x1D
        self.musicVolume = None     # range: [0x00, 0x1F],               default: 0x19
        self.joystickType = None    # range: ["none" ... "side_winder"], default: "none"
        self.throttle = None        # range: ["no" .. "reversed"],       default: "no"
        self.footPedals = None      # range: ["no", "yes"],              default: "no"
        self.redOutBlackOut = None  # range: ["no", "yes"],              default: "yes"

        with open(self._path, "rb") as f:
            # resolution (0: 300x200, 1:400x300, 2:640x480)
            if f.read(1) != b'\x02':
                raise ConfigError("invalid config file %s" % (path))

            if f.read(4) != b'\x01\x01\x01\x17':
                raise ConfigError("invalid config file %s" % (path))

            self.sfxVolume = int.from_bytes(f.read(1), "little")
            self.musicVolume = int.from_bytes(f.read(1), "little")

            try:
                self.joystickType = self._joystickTypeDict[f.read(1)]
            except KeyError:
                raise ConfigError("invalid config file %s" % (path))

            try:
                self.throttle = self._throttleTypeDict[f.read(1)]
            except KeyError:
                raise ConfigError("invalid config file %s" % (path))

            try:
                self.footPedals = self._footPedalTypeDict[f.read(1)]
            except KeyError:
                raise ConfigError("invalid config file %s" % (path))

            try:
                self.redOutBlackOut = self._redOutBlackOutTypeDict[f.read(1)]
            except KeyError:
                raise ConfigError("invalid config file %s" % (path))

            if f.read(53) != b'\x00' * 53:
                raise ConfigError("invalid config file %s" % (path))

    def save(self, filepath=None):
        if filepath is None:
            filepath = self._path

        buf = b'\x02\x01\x01\x01\x17'
        buf += self.sfxVolume.to_bytes(1, "little")
        buf += self.musicVolume.to_bytes(1, "little")
        buf += self._joystickTypeDict.inverse[self.joystickType]
        buf += self._throttleTypeDict.inverse[self.throttle]
        buf += self._footPedalTypeDict.inverse[self.footPedals]
        buf += self._redOutBlackOutTypeDict.inverse[self.redOutBlackOut]
        buf += b'\x00' * 53
        pathlib.Path(filepath).write_bytes(buf)

        self._path = filepath
